﻿using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;

namespace TFSRestApiHelper
{
    enum ClientMethod
    {
        GET, POST, PATCH, PUT
    }

    public static class RestApiHelper
    {
        private static string TFSUrl = ""; // Uri to TFS
        private static string TFSCln = ""; // TFS Projects Collection
        private static string urlCreateWI = "{0}/{1}/{2}/_apis/wit/workitems/${3}?api-version=1.0";
        private static string urlUpdateWI = "{0}/{1}/_apis/wit/workitems/{2}?api-version=1.0";
        private static string QueryWisReq = "{0}/{1}/_apis/wit/wiql?api-version=1.0";
        private static string QieryByIdsFromActiveResult = "{0}/{1}/_apis/wit/WorkItems?ids={2}&fields=System.Id,System.Title,System.WorkItemType,System.TeamProject&api-version=1.0";
        private static string QueryWorkItem = "{0}/{1}/_apis/wit/workitems/{2}?api-version=1.0";
        private static string QueryWorkItemType = "{0}/{1}/{2}/_apis/wit/workitemtypes/{3}?api-version=1.0";
        private static string UserName = "",  Password = "", Domain = "";        
        private static string DisplayName = "";
        private static bool UseDefCreds = true;
        private static string ActiveState = "";
        private static string Activity = "";
        private static string _exceptions = "";

        public static string Exceptions { get { return _exceptions; } set { _exceptions = value; } }

        public static string TFSUserName
        {
            get { return UserName; }
            set { UserName = value; }
        }

        public static string TFSPassword
        {
            get { return Password; }
            set { Password = value; }
        }

        public static string TFSDomain
        {
            get { return Domain; }
            set { Domain = value; }
        }

        public static string TFSServiceUrl
        {
            get { return TFSUrl; }
            set { TFSUrl = value; }
        }

        public static string TFSCollection
        {
            get { return TFSCln; }
            set { TFSCln = value; }
        }

        public static bool UseDefaultCredentials
        {
            get { return UseDefCreds; }
            set { UseDefCreds = value; }
        }

        public static string WIActiveState
        {
            get { return ActiveState; }
            set { ActiveState = value; }
        }

        public static string WIActivity
        {
            get { return Activity; }
            set { Activity = value; }
        }

        public static string UserDisplayName
        {
            get { return DisplayName; }
            set { DisplayName = value; }
        }


        /// <summary>
        /// Get workitem by id
        /// </summary>
        /// <param name="pId"></param>
        /// <returns></returns>
        public static async Task<WorkItemAtrr> GetWorkItem(int pId)
        {
            try
            {
                string _query_url = String.Format(QueryWorkItem, TFSUrl, TFSCln, pId.ToString());

                HttpResponseMessage _response = await DoRequest(_query_url, null, ClientMethod.GET);

                if (_response != null)
                {
                    if (_response.IsSuccessStatusCode)
                    {
                        string _responseStr = await _response.Content.ReadAsStringAsync();

                        return JsonConvert.DeserializeObject<WorkItemAtrr>(_responseStr);
                    }
                    else
                    {
                        string _responseStr = await _response.Content.ReadAsStringAsync();
                        var _error = JsonConvert.DeserializeObject<WIException>(_responseStr);

                        AddExceptions(_error);
                    }
                }
            }
            catch(Exception _ex)
            {
                AddExceptions(_ex);
            }

            return null;
        }        

        /// <summary>
        /// Get workitem type from project
        /// </summary>
        /// <param name="pProject">Project name</param>
        /// <param name="pWIType">Work item type name</param>
        /// <returns></returns>
        public static async Task<WorkItemTypeAtrr> GetWorkItemType(string pProject, string pWIType)
        {
            try
            {
                string _query_url = String.Format(QueryWorkItemType, TFSUrl, TFSCln, pProject, pWIType);


                HttpResponseMessage _response = await DoRequest(_query_url, null, ClientMethod.GET);

                if (_response != null)
                {
                    if (_response.IsSuccessStatusCode)
                    {
                        string _responseStr = await _response.Content.ReadAsStringAsync();

                        return JsonConvert.DeserializeObject<WorkItemTypeAtrr>(_responseStr);
                    }
                    else
                    {
                        string _responseStr = await _response.Content.ReadAsStringAsync();
                        var _error = JsonConvert.DeserializeObject<WIException>(_responseStr);

                        AddExceptions(_error);
                    }
                }
            }
            catch (Exception _ex)
            {
                AddExceptions(_ex);
            }

            return null;
        }

        /// <summary>
        /// Build Active workitems menu
        /// </summary>
        public static async Task<ActiveWIList> GetWorkItems()
        {
            FlatQueryResult _queryresult = null;
            _queryresult = await GetActiveWorkItemList();

            if (_queryresult == null || _queryresult.workItems == null || _queryresult.workItems.Count == 0) return null;

            return await QueryActiveWorkItemsDetail(_queryresult);
        }

        /// <summary>
        /// Get details for ActiveWIList struct
        /// </summary>
        /// <param name="pQueryResult"></param>
        /// <returns></returns>
        private static async Task<ActiveWIList> QueryActiveWorkItemsDetail(FlatQueryResult pQueryResult)
        {
            try
            {
                string _wiids = "";
                foreach (FlatQueryResult.QueryWorkItem _wi in pQueryResult.workItems)
                {
                    if (_wiids == "") _wiids = _wi.id.ToString();
                    else _wiids += "," + _wi.id.ToString();
                }

                string _query_url = String.Format(QieryByIdsFromActiveResult, TFSUrl, TFSCln, _wiids);

                HttpResponseMessage _response = await DoRequest(_query_url, null, ClientMethod.GET);

                if (_response != null)
                {
                    if (_response.IsSuccessStatusCode)
                    {
                        string _responseStr = await _response.Content.ReadAsStringAsync();

                        return JsonConvert.DeserializeObject<ActiveWIList>(_responseStr);
                    }
                    else
                    {
                        string _responseStr = await _response.Content.ReadAsStringAsync();
                        var _error = JsonConvert.DeserializeObject<WIException>(_responseStr);

                        AddExceptions(_error);
                    }
                }
            }
            catch (Exception _ex)
            {
                AddExceptions(_ex);
            }

            return null;
        }

        /// <summary>
        /// Get only active workites for active user
        /// </summary>
        /// <returns></returns>
        private static async Task<FlatQueryResult> GetActiveWorkItemList()
        {
            try
            {
                string _query_url = String.Format(QueryWisReq, TFSUrl, TFSCln); 

                WIQLQueryAtrr _wiql = new WIQLQueryAtrr();
                _wiql.query = "Select [System.Id], [System.Title], [System.State] From WorkItems Where [System.State] = '" + ActiveState + "' AND [System.AssignedTo] = '" + DisplayName + "'";

                HttpResponseMessage _response = await DoRequest(_query_url, JsonConvert.SerializeObject(_wiql), ClientMethod.POST);

                if (_response != null)
                {
                    if (_response.IsSuccessStatusCode)
                    {
                        string _responseStr = await _response.Content.ReadAsStringAsync();

                        return JsonConvert.DeserializeObject<FlatQueryResult>(_responseStr);
                    }
                    else
                    {
                        string _responseStr = await _response.Content.ReadAsStringAsync();
                        var _error = JsonConvert.DeserializeObject<WIException>(_responseStr);

                        AddExceptions(_error);
                    }
                }
            }
            catch (Exception _ex)
            {
                AddExceptions(_ex);
            }

            return null;
        }

        /// <summary>
        /// Create work item and link to parent work item
        /// </summary>
        /// <param name="pProject"></param>
        /// <param name="pWorkItemType"></param>
        /// <param name="pFields"></param>
        /// <param name="pParentUrl"></param>
        public static async void CreateChildWorkItem(string pProject, string pWorkItemType, Dictionary<String, String> pFields, string pParentUrl)
        {
            try
            {
                string _query_url = String.Format(urlCreateWI, TFSUrl, TFSCln, pProject, pWorkItemType);

                List<Object> flds = new List<Object>();

                foreach (var _key in pFields.Keys) flds.Add(new NewField { op = "add", path = "/fields/" + _key, value = pFields[_key] });

                NewLinks _lnks = new NewLinks();
                _lnks.value.rel = "System.LinkTypes.Hierarchy-Reverse";
                _lnks.value.url = pParentUrl;
                _lnks.value.attributes.comment = "TimeSheet";
                _lnks.value.attributes.isLocked = true;

                flds.Add(_lnks);

                HttpResponseMessage _response = await DoRequest(_query_url, JsonConvert.SerializeObject(flds), ClientMethod.PATCH);

                if (_response != null)
                {
                    if (_response.IsSuccessStatusCode)
                    {
                        string _responseStr = await _response.Content.ReadAsStringAsync();

                        var _wi = JsonConvert.DeserializeObject<WorkItemAtrr>(_responseStr);
                    }
                    else
                    {
                        string _responseStr = await _response.Content.ReadAsStringAsync();
                        var _error = JsonConvert.DeserializeObject<WIException>(_responseStr);

                        AddExceptions(_error);
                    }
                }
            }
            catch (Exception _ex)
            {
                AddExceptions(_ex);
            }
        }

        /// <summary>
        /// Update fields for existing work item
        /// </summary>
        /// <param name="pId"></param>
        /// <param name="pFields"></param>
        public static async void UpdatedWorkItem(int pId, Dictionary<String, String> pFields)
        {
            try
            {
                string _query_url = String.Format(urlUpdateWI, TFSUrl, TFSCln, pId);

                List<Object> flds = new List<Object>();

                foreach (var _key in pFields.Keys)
                    flds.Add(new NewField { op = "add", path = "/fields/" + _key, value = pFields[_key] });

                HttpResponseMessage _response = await DoRequest(_query_url, JsonConvert.SerializeObject(flds), ClientMethod.PATCH);

                if (_response != null)
                {
                    if (_response.IsSuccessStatusCode)
                    {
                        string _responseStr = await _response.Content.ReadAsStringAsync();

                        var _wi = JsonConvert.DeserializeObject<WorkItemAtrr>(_responseStr);
                    }
                    else
                    {
                        string _responseStr = await _response.Content.ReadAsStringAsync();
                        var _error = JsonConvert.DeserializeObject<WIException>(_responseStr);

                        AddExceptions(_error);
                    }
                }
            }
            catch (Exception _ex)
            {
                AddExceptions(_ex);
            }
        }

        /// <summary>
        /// Execite request to TFS
        /// </summary>
        /// <param name="pRequest"></param>
        /// <param name="pBody"></param>
        /// <param name="pClientMethod"></param>
        /// <returns></returns>
        private static async Task<HttpResponseMessage> DoRequest(string pRequest, string pBody, ClientMethod pClientMethod)
        {
            try
            {
                HttpClientHandler _httpclienthndlr = new HttpClientHandler();                
                                
                if (UseDefCreds) _httpclienthndlr.Credentials = CredentialCache.DefaultCredentials;
                else if (Domain == "") _httpclienthndlr.Credentials = new NetworkCredential(UserName, Password);
                else _httpclienthndlr.Credentials = new NetworkCredential(UserName, Password, Domain);


                using (HttpClient _httpClient = new HttpClient(_httpclienthndlr))
                {
                    switch (pClientMethod)
                    {
                        case ClientMethod.GET:
                            return await _httpClient.GetAsync(pRequest);

                        case ClientMethod.POST:
                            return await _httpClient.PostAsync(pRequest, new StringContent(pBody, Encoding.UTF8, "application/json"));

                        case ClientMethod.PATCH:
                            var _request = new HttpRequestMessage(new HttpMethod("PATCH"), pRequest);
                            _request.Content = new StringContent(pBody, Encoding.UTF8, "application/json-patch+json");                            
                            return await _httpClient.SendAsync(_request);

                        default:
                            return null;
                    }
                                        
                }
            }
            catch (Exception _ex)
            {
                AddExceptions(_ex);
                return null;
            }
        }

        /// <summary>
        /// Add exceptions to main variable
        /// </summary>
        /// <param name="_ex"></param>
        private static void AddExceptions(Exception _ex)
        {
            _exceptions += "----------------------" + DateTime.Now.ToString() + "----------------------\n";
            if (_ex.Message != null) _exceptions += _ex.Message + "\n";
            if (_ex.StackTrace != null) _exceptions += _ex.StackTrace + "\n";
        }

        /// <summary>
        /// Add exceptions to main variable
        /// </summary>
        /// <param name="_error"></param>
        private static void AddExceptions(WIException _error)
        {
            _exceptions += "----------------------" + DateTime.Now.ToString() + "----------------------\n";
            _exceptions += "eventId: " + _error.eventId.ToString() + "\n";
            _exceptions += "errorCode: " + _error.errorCode.ToString() + "\n";
            _exceptions += "id: " + _error.id.ToString() + "\n";
            if (_error.innerException !=  null) _exceptions += "innerException: " + _error.innerException.ToString() + "\n";
            if (_error.message != null) _exceptions += "message: " + _error.message.ToString() + "\n";
            if (_error.typeKey != null) _exceptions += "typeKey: " + _error.typeKey.ToString() + "\n";
            if (_error.typeName != null) _exceptions += "typeName: " + _error.typeName.ToString() + "\n";
        }
    }
}
